using System;
using System.Collections.Generic;
using UnityEngine;

namespace MLTimeline
{
    public class TimelinePlayer : MonoBehaviour
    {
        #region 配置与状态
        [Header("配置")]
        public Timeline timeline = new();
        public LoopMode loopMode = LoopMode.Once;
        public int loopCount = 1;

        [Header("播放状态 (只读)")]
        private bool _isPlaying = false;
        private int _currentFrame = 0;
        private int _direction = 1;
        #endregion

        #region 内部核心系统
        // 时间驱动
        private float _logicAccumulator = 0.0f;
        private float _secondsPerFrame;
        private const int MAX_FRAMES_PER_UPDATE = 10;
        private int _currentLoopCount = 0;

        // 事件驱动
        private struct TimelineEvent : IComparable<TimelineEvent>
        {
            public int frame;
            public enum EventType { Start, End }
            public EventType type;
            public IClip clip;
            public readonly int CompareTo(TimelineEvent other) => frame.CompareTo(other.frame);
        }
        private readonly List<TimelineEvent> _sortedEvents = new();
        private readonly List<IClip> _clipsToAdd = new();
        private readonly List<IClip> _clipsToRemove = new();
        private readonly HashSet<IClip> _activeClips = new();
        private int _nextEventIndex = 0;
        #endregion

        #region 公共控制接口

        public void Initialize()
        {
            timeline?.Initialize();
            // 构建事件表
            _sortedEvents.Clear();
            for (var i = 0; i < timeline.tracks.Count; i++)
            {
                var track = timeline.tracks[i];
                if (track == null) continue;
                if (track.isMuted) continue;
                foreach (var clip in track.clips)
                {
                    _sortedEvents.Add(new TimelineEvent { frame = clip.StartFrame, type = TimelineEvent.EventType.Start, clip = clip });
                    _sortedEvents.Add(new TimelineEvent { frame = clip.EndFrame, type = TimelineEvent.EventType.End, clip = clip });
                }
            }
            _sortedEvents.Sort();
        }
        public void Play() => StartPlayback(1);
        public void PlayReverse() => StartPlayback(-1);
        public void Pause() => _isPlaying = false;
        public void Resume() { if (timeline != null) _isPlaying = true; }
        public void Stop()
        {
            _isPlaying = false;
            // 在停止前，确保所有活动剪辑都已退出
            foreach (var clip in _activeClips)
            {
                clip.Exit();
            }
            ResetPlaybackState();
        }

        /// <summary>
        /// 跳转到指定帧并立即更新状态。
        /// </summary>
        /// <param name="targetFrame">要跳转的目标帧。</param>
        /// <param name="evaluate">为true时，会立即评估目标帧的状态并更新所有相关对象。</param>
        public void JumpToFrame(int targetFrame, bool evaluate = true)
        {
            if (timeline == null || _sortedEvents.Count == 0) return;

            // 跳转时总是先停止播放
            _isPlaying = false;

            // 1. 清理当前所有活动剪辑的“进入”状态
            foreach (var clip in _activeClips)
            {
                clip.Exit();
            }
            _activeClips.Clear();

            // 2. 设置当前帧，并确保其在有效范围内
            _currentFrame = Mathf.Clamp(targetFrame, 0, timeline.TotalFrames);

            // 3. 【核心】重新计算并设置在该帧应该激活的剪辑
            // 我们需要遍历所有剪辑，找出那些生命周期包含目标帧的剪辑
            foreach (var track in timeline.tracks)
            {
                if (track.isMuted) continue;
                foreach (var clip in track.clips)
                {
                    if (clip.StartFrame <= _currentFrame && clip.EndFrame >= _currentFrame)
                    {
                        AddToActive(clip);
                    }
                }
            }

            // 4. 【性能关键】高效地重新定位事件指针 _nextEventIndex
            // 我们使用二分查找来替代慢速的线性扫描，以定位到下一个应该处理的事件
            var searchEvent = new TimelineEvent { frame = _currentFrame };
            int searchIndex = _sortedEvents.BinarySearch(searchEvent);

            if (searchIndex < 0)
            {
                // 如果没找到精确匹配，BinarySearch返回一个负数。
                // 按位取反(~)可以得到第一个“大于”目标帧的事件的索引。
                _nextEventIndex = ~searchIndex;
            }
            else
            {
                // 如果找到了精确匹配，我们需要确保指针指向该帧的第一个事件
                while (searchIndex > 0 && _sortedEvents[searchIndex - 1].frame == _currentFrame)
                {
                    searchIndex--;
                }
                _nextEventIndex = searchIndex;
            }

            // 5. 如果需要，立即评估当前帧的状态
            if (evaluate)
            {
                ProcessEvaluation(_currentFrame);
            }
        }

        #endregion

        #region Unity生命周期
        void Awake()
        {
            Initialize();
        }

        void Update()
        {
            if (!_isPlaying) return;
            ProcessFrameAdvancement();
        }

#if UNITY_EDITOR
        private void OnValidate()
        {
            if (Application.isPlaying) return;
            Initialize();
        }
#endif
        #endregion

        #region 内部核心实现

        private void StartPlayback(int direction)
        {
            if (timeline == null)
            {
                Debug.LogError("timeline is not assigned!");
                return;
            }
            _direction = direction;
            InitializeForPlayback();
            _isPlaying = true;
        }

        private void InitializeForPlayback()
        {
            _secondsPerFrame = 1.0f / timeline.frameRate;
            _currentLoopCount = 0;
            _logicAccumulator = 0.0f;

            // 根据播放方向初始化状态
            ResetPlaybackState();
        }

        private void ProcessFrameAdvancement()
        {
            _logicAccumulator += Time.deltaTime;
            int framesProcessed = 0;

            while (_logicAccumulator >= (_secondsPerFrame - float.Epsilon) && framesProcessed < MAX_FRAMES_PER_UPDATE)
            {
                _logicAccumulator -= _secondsPerFrame;
                EvaluateFrame(_currentFrame);
                _currentFrame += _direction;
                framesProcessed++;

                bool boundaryReached = (_direction == 1 && _currentFrame > timeline.TotalFrames) || (_direction == -1 && _currentFrame < 0);
                if (boundaryReached)
                {
                    HandleBoundary();
                    if (!_isPlaying) break;
                }
            }
        }

        private void EvaluateFrame(int frame)
        {
            ProcessAndCategorizeEvents(frame);
            if (_clipsToAdd.Count > 0)
            {
                foreach (var clip in _clipsToAdd) AddToActive(clip);
            }
            ProcessEvaluation(frame);
            if (_clipsToRemove.Count > 0)
            {
                foreach (var clip in _clipsToRemove) RemoveFromActive(clip);
            }
        }

        private void ProcessAndCategorizeEvents(int frame)
        {
            _clipsToAdd.Clear();
            _clipsToRemove.Clear();
            if (_direction == 1)
            {
                while (_nextEventIndex < _sortedEvents.Count)
                {
                    var evt = _sortedEvents[_nextEventIndex];
                    if (evt.frame > frame) break;
                    if (evt.frame == frame)
                    {
                        if (evt.type == TimelineEvent.EventType.Start)
                        {
                            _clipsToAdd.Add(evt.clip);
                        }
                        else _clipsToRemove.Add(evt.clip);
                    }
                    _nextEventIndex++;
                }
            }
            else
            {
                while (_nextEventIndex >= 0)
                {
                    var evt = _sortedEvents[_nextEventIndex];
                    if (evt.frame < frame) break;
                    if (evt.frame == frame)
                    {
                        if (evt.type == TimelineEvent.EventType.Start)
                        {
                            _clipsToRemove.Add(evt.clip);
                        }
                        else _clipsToAdd.Add(evt.clip);
                    }
                    _nextEventIndex--;
                }
            }
        }

        private void ProcessEvaluation(int frame)
        {
            foreach (var activeClip in _activeClips)
            {
                var progress = 1f;
                if (activeClip.DurationInFrames > 0)
                {
                    progress = (float)(frame - activeClip.StartFrame) / activeClip.DurationInFrames;
                    progress = Mathf.Clamp01(progress);
                }
                Debug.Log($"<color=yellow>EVALUATE ({frame}/{activeClip.StartFrame}/{activeClip.EndFrame}):</color> '{activeClip.GetType().Name}'");
                activeClip.Evaluate(frame, progress, timeline.frameRate);
            }
        }

        private void AddToActive(IClip clip)
        {
            if (_activeClips.Add(clip)) // HashSet.Add 返回是否添加成功
            {
                Debug.Log($"<color=green>ADD ({_currentFrame}/{clip.StartFrame}/{clip.EndFrame}):</color> Added clip '{clip.GetType().Name}'");
                clip.Enter();
            }
        }

        private void RemoveFromActive(IClip clip)
        {
            if (_activeClips.Remove(clip))
            {
                clip.Exit();
                Debug.Log($"<color=red>REMOVE ({_currentFrame}/{clip.StartFrame}/{clip.EndFrame}):</color> Removed clip '{clip.GetType().Name}'");
            }
        }

        private void HandleBoundary()
        {
            if (loopMode == LoopMode.Once)
            {
                Stop();
                return;
            }

            _currentLoopCount++;
            if (loopCount > 0 && _currentLoopCount >= loopCount)
            {
                Stop();
                return;
            }

            switch (loopMode)
            {
                case LoopMode.Loop:
                    ResetPlaybackState();
                    break;
                case LoopMode.PingPong:
                    _direction *= -1;
                    // 将播放头“粘”在它刚刚离开的边界上。
                    // 这样，主循环的下一次迭代就会用新的方向再次评估这个边界帧。
                    if (_direction == -1) // 刚刚是正向播放，现在将反向
                    {
                        _currentFrame = timeline.TotalFrames;
                        // 将事件指针重置到列表末尾，为反向播放做准备
                        _nextEventIndex = _sortedEvents.Count - 1;
                    }
                    else // 刚刚是反向播放，现在将正向
                    {
                        _currentFrame = 0;
                        // 将事件指针重置到列表开头，为正向播放做准备
                        _nextEventIndex = 0;
                    }
                    // 注意：这里不需要调用ResetPlaybackState，事件指针和活动剪辑的状态需要保留。
                    break;
                case LoopMode.PingPongEdge:
                    _direction *= -1;
                    // 当播放顺序被打破时（如PingPong的转向），最可靠的方式是执行一次“跳转”。
                    // JumpToFrame 会完美地重建 _activeClips 集合，并精确地重定位 _nextEventIndex 事件指针，
                    // 确保后续的播放状态是完全正确的。
                    // 我们传递 evaluate: false 是因为主循环的下一次迭代会自然地进行评估，避免重复。
                    if (_direction == -1) // 刚刚是正向播放，现在将从 (总帧数 - 1) 开始反向播放
                    {
                        // 确保TotalFrames > 0，避免负数帧
                        int targetFrame = Mathf.Max(0, timeline.TotalFrames - 1);
                        JumpToFrame(targetFrame, false);
                    }
                    else // 刚刚是反向播放，现在将从第 1 帧开始正向播放
                    {
                        // 目标是第1帧，因为第0帧已经播放过了
                        int targetFrame = 1;
                        if (targetFrame >= timeline.TotalFrames) targetFrame = 0; // 防止Timeline过短
                        JumpToFrame(targetFrame, false);
                    }

                    // JumpToFrame 会默认暂停播放，我们必须在这里将其恢复，以继续循环。
                    _isPlaying = true;
                    break;
            }
        }

        private void ResetPlaybackState()
        {
            _activeClips.Clear();
            if (_direction == 1)
            {
                _currentFrame = 0;
                _nextEventIndex = 0;
            }
            else
            {
                _currentFrame = timeline.TotalFrames;
                _nextEventIndex = _sortedEvents.Count > 0 ? _sortedEvents.Count - 1 : 0;
            }
        }
        #endregion
    }
}